package project

import (
	"fmt"
	"net"
	"strings"
	"time"
)

type User struct {
	id   string
	name string
	msg  chan string
}

// 创建一个全局的map结构，用于保存所有的用户
var allUser = make(map[string]User)

// 创建message广播通道
var message = make(chan string, 10)

func Serv() {
	//创建监听器
	listen, err := net.Listen("tcp", "127.0.0.1:8082")
	if err != nil {
		fmt.Println("监听失败...", err)
		return
	}
	//启动全局唯一的go程，负责监听message通道，写给所有的用户
	go Broadcast()

	for {
		//建立连接
		fmt.Println("服务启动成功，监听中......")
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("建立连接失败...")
			return
		}
		fmt.Println("建立连接成功...")
		go Handt(conn)
	}
}

// 处理具体业务
func Handt(conn net.Conn) {
	fmt.Println("启动业务...")
	//客户端和服务端建立连接的时候，会有ip和port，当成user的id
	clientAddr := conn.RemoteAddr().String()
	fmt.Println("客户端地址是：", clientAddr)

	//创建user
	newUser := User{
		id:   clientAddr,            //id,我们不会修改，这个作为在map中的key
		name: clientAddr,            //可以修改，会提供rename命令修改，建立连接时，初始值与id相同
		msg:  make(chan string, 10), //注意需要make空间，否则无法写入数据
	}
	//添加user到map结构
	allUser[newUser.id] = newUser

	//定义一个退出信号，用于监听client退出
	var isQuit = make(chan bool)

	//创建一个用于重置计数器的管道，用于告知watch函数，当前用户输入
	var restTimer = make(chan bool)

	//启动go程，负责监听退出信号
	go Watch(&newUser, conn, isQuit, restTimer)

	//启动go程，负责将msg信息返回给客户端
	go WriteBackToClient(&newUser, conn)

	//向message写入数据，当前用户上线的消息，用于广播通知所有人
	//拼接字符串用fmt.Sprinf
	loginInfo := fmt.Sprintf("[%s]:[%s]---->上线了，login！！\n", newUser.id, newUser.name)
	message <- loginInfo

	for {
		//创建一个容器，用于接收数据
		buf := make([]byte, 1024)

		cnt, err := conn.Read(buf)
		if err != nil {
			fmt.Println("读取数据失败...")
			return
		}
		if cnt == 0 {
			fmt.Println("客户端主动关闭ctrl + c,准备退出！")
			//map删除用户，conn.close
			//服务器还可以主动的退出
			//在这里不进行真正的退出动作，而是发送一个退出信号，统一做退出处理
			isQuit <- true
		}

		fmt.Println("service接收client发来的数据，长度：", cnt, "数据：", string(buf[:cnt-1]))

		//---业务逻辑处理 开始
		//1、查询当前所有的用户。输入 who
		//a.先判断接收的数据是不是who  ===》 长度和字符串
		userInput := string(buf[:cnt-1]) //这是用户输入的数据，最后一个是回车，我们去掉它
		if len(userInput) == 4 && userInput == "\\who" {
			//b.遍历alluser这个map：key ：= user.id value :user,将id和那么拼接成一个字符串，但会给客户端
			fmt.Println("用户即将查询所有用户信息!")
			//TODO

			//这个切片包含所有的用户信息
			var userInfos []string

			//[]string("userid:23,username:zz","userid:10,username:hh")
			for _, user := range allUser {
				userInfo := fmt.Sprintf("userid:%s,username:%s", user.id, user.name)
				userInfos = append(userInfos, userInfo)
			}

			//最终写到管道中，一定是一个字符串
			r := strings.Join(userInfos, "\n")

			//将数据返回给查询的客户端
			newUser.msg <- r

		} else if len(userInput) > 9 && userInput[:6] == "\\rename" {
			//规则： rename|duke
			//1、读取数据判断长度>8，判断字符是rename
			//2、使用|进行分割，获取|后面的部分，作为名字
			//3、更新用户名字user.name = duck
			newUser.name = strings.Split(userInput, "|")[1]
			allUser[newUser.id] = newUser //更新map中的user

			// 4、通知客户，更新成功
			newUser.msg <- "rename successfully!"

		} else {
			//如果用户输入的不是命令，只是普通的聊天信息，那么只需要写到广播通道中即可，由其他go程进行常规转发
			message <- userInput
		}

		//---业务逻辑处理 结束

	}

}

// 向所有的用户广播消息,启动一个全局唯一go程
func Broadcast() {
	fmt.Println("广播go程启动成功")
	defer fmt.Println("Broadcast 程序退出!")

	for {
		fmt.Println("Broadcast 监听message中...")

		//1、从message中读取数据
		info := <-message
		fmt.Println("message接收到的消息:\n", info)

		//2、将数据写入到每一个用户的msg管道中
		for _, user := range allUser {
			user.msg <- info
		}
	}
}

// 每个用户应该还有一个用户监听自己的msg管道的go，负责将数据返回给客户端
func WriteBackToClient(user *User, conn net.Conn) {
	//TODO
	fmt.Printf("user：%s的go程正在监听自己的msg管道：\n", user.name)
	for data := range user.msg {
		fmt.Printf("user:%s写回给客户端的数据:%s\n", user.name, data)
		_, _ = conn.Write([]byte(data))
	}
}

// 启动一个go程，负责监听退出信号，触发后，进行清零工作:delete map,conn.close都在这里处理
func Watch(user *User, conn net.Conn, isQuit, restTimer <-chan bool) {
	fmt.Println("启动监听退出信号的go程...")
	defer fmt.Println("Watch go程退出!")
	for {
		select {
		case <-isQuit:
			logoutInfo := fmt.Sprintf("%s exit already!\n", user.name)
			fmt.Println("删除当前用户:", user.name)
			delete(allUser, user.id)
			message <- logoutInfo
			conn.Close()
			return
		case <-time.After(time.Second * 10): //超时退出
			logoutInfo := fmt.Sprintf("%s timeout exit already!\n", user.name)
			fmt.Println("删除当前用户:", user.name)
			delete(allUser, user.id)
			message <- logoutInfo
			conn.Close()
			return
		case <-restTimer:
			fmt.Printf("连接%s 重置计数器\n", user.name)
		}
	}
}
